本文为您详细介绍呼叫中心SDK前端接入流程。

注意 当前版本仅做维护,后续不会继续迭代。如有需求,请参见热线SDK接入(新版)

引用SDK

  1. SDK cdn assets(umd module)

    https://g.alicdn.com/xspace/phone/0.5.1/sdk.js

  2. SDK Preview Sandbox

    https://g.alicdn.com/xspace/phone/0.5.1/index.html#/sample/use-sdk

依赖

  1. SIM-API:https://g.alicdn.com/crm/sipml-api/0.0.8/SIPml-api.js
  2. HTTP:https://g.alicdn.com/xspace/phone/0.5.1/http.js
启用渲染拨号盘会自动加载以上依赖。加载后即可获取SDK实例内容:
const { SoftPhoneSDK } = window

前提条件

初始化前需要完成以下准备工作:
  1. 必须使用Chrome浏览器,版本号为58以上。原因是云呼叫中心的通话是通过webRTC技术实现的,目前Chrome浏览器对于webRTC技术的支持是最好的。为了保证您的通话质量及安全性,所以我们做出了这样的要求。
  2. 软电话SDK所嵌入的自有业务系统必须使用HTTPS协议。原因是Chrome在47版本之后,禁止HTTP协议获取系统麦克风权限,会造成无法正常通话。
  3. 如果您是在iframe标签内使用软电话SDK,那么需要为iframe标签增加allow="microphone"属性,来允许iframe标签获取系统麦克风权限。

接入手册

1
状态流转
  • 蓝色为流转状态触发的事件hook,请参见事件回调枚举。
  • 白色为坐席状态,请参见坐席状态枚举。
  • 加载SDK初始坐席状态为-1表示未注册,需要申请InstanceId BearerToken进行签入坐席操作。每个坐席原则意义上按1人/位,可能存在设置同一坐席公用。具体流程如下:2
  1. 软电话类
    const { SoftPhone } = window.SoftPhoneSDK;
    const softPhone = SoftPhone.getInstance(); // softphone follow Singleton Pattern

    通过getInstance方法生成唯一实例。

    • 实例API
      API type description
      setConfig (config: Partial<SDKConfig> = {}) 注入配置,如下所示
      register (eventName: EnumEventName, cb: (eventData: { type: EnumEventName, preConfig: Partial<SDKConfig>, data: any)} => void ) => Canceler 注册LifeCycle事件返回其注销方法。
      getUIComponent () => React.ElementType 获取拨号条组件实例,配合APIs.renderComponent 渲染

      使用getUIComponent获取的组件必须使用APIs.renderComponent去挂载,这是因为SDK内部集成了React&ReactDOM,使用外部版本挂载会导致版本不通或者双副本的问题。

    • SDKConfig Options
      option type defaultvalue description
      InstanceId string "" 实例ID,可在阿里云控制台使用主账号登录实例列表获取https://ccc.console.aliyun.com/AccInstance。
      BearerToken string "" bearerToken,通过三方账号授权置换token中的access_token。
      env "online" | "preOnline" 'online' 配置环境。
      apiHost "scsp.aliyuncs.com" | "aiccs.aliyuncs.com" | "api.rhinokeen.com" 'scsp.aliyuncs.com' API服务器。
      Version string '2020-07-02' POP API版本号。
      • scsp.aliyun.com对应版本为'2020-07-02'
      • aiccs.aliyun.com对应版本为'2019-10-15'
      • api.rhinekeen.com无需版本
      locale EnumLocale EnumLocale['zh-CN'] 国际化。
      isPlugin boolean true 拨号条插件是否支持拖拽, 默认支持。
      needDesensitize boolean false 是否对入呼或外呼号码脱敏展示,默认不脱敏。
      autoChangeOnLineTime number 1 自定义话后处理时间,单位为s(秒),为0代表不需要自动切状态,默认为1s自动结束话后处理。
      canChangeStatusByHand boolean false 是否能够手动切换状态的能力,默认为false,不展示操作栏&不支持手动更新。
      enableVoiceToText Array<'callin' | 'callout'> [] 启用语音转文本,该能力需要BU配置支持。
      enableServiceSummary boolean false 启用服务摘要。
      disableUI boolean false 是否隐藏UI,默认为false,不隐藏。
      cdnPath string //g.alicdn.com 内置资源默认引用CDN域。
    • Demo
      const config =  {
        InstanceId: 'ccc_xp_pre-cn-78v1gnp97002',   // 实例id,可在阿里云控制台实例列表获取
        BearerToken: '',                            // bearerToken,通过【三方账号授权】置换token中的access_token
        env: 'online',                              // online || preOnline, 
        apiHost: 'api.rhinokeen.com',               // scsp.aliyuncs.com || aiccs.aliyuncs.com || api.rhinokeen.com
        Version: '',                                // Pop Api 版本号,默认为'2020-07-02'. scsp.aliyun.com对应版本为'2020-07-02';aiccs.aliyun.com对应版本为'2019-10-15';api.rhinekeen.com无需版本
        locale: EnumLocale['zh-CN'],
        isPlugin: true,                             // 插件是否支持拖拽,默认支持
        needDesensitize: false,                     // 是否对入呼/外呼号码脱敏展示,默认不脱敏
        autoChangeOnLineTime: 1,                    // 自定义话后处理时间,单位为s,为0代表不需要自动切状态,默认为1s自动结束话后处理
        canChangeStatusByHand: false,               // 是否能够手动切换状态
        disableUI: false                            // 是否隐藏UI, 默认为false,不隐藏
      };
      softPhone.setConfig(config);
      softPhone.register(EnumEventName.actionError, () => {
        softPhone.setConfig({
          BearerToken: 'newBearerToken'             // token失效后,此处进行事件注册,设置新的token
        });
      });
      
      const SoftPhoneUI: React.ElementType<any> = softPhone.getUIComponent();
      
      // if use React Framework, you can render component directly
      const ExampleApp: React.FC = () => {
        return (
          <SoftPhoneUI />
        );
      }
      // if not use React Framework, you can render softphone to specified ElementNode
      APIs.renderComponent(SoftPhoneUI, document.querySelector('#phone-container'));
  2. LifeCycle事件注册

    旨在各调度过程中触发的钩子,采用订阅派发的模式进行管理。具体流转状态,请参见流转状态流程图。

    • 事件回调枚举
      export enum EnumEventName {
        actionError = 'actionError', // 出错
        actionSuccess = 'actionSuccess', // 拨号盘流程回调
        onAgentCheckedin = 'onAgentCheckedin', // 上班签入成功回调
        onAgentCheckedout = 'onAgentCheckedout', // 下班签出成功回调
        onCallComing = 'onCallComing', // 新来电振铃回调
        onCallDialOut = 'onCallDialOut', // 外拨请求中回调
        onCallWillAnswer = 'onCallWillAnswer', // 呼入电话执行接听动作回调
        onCallEstablishForCallin = 'onCallEstablishForCallin', // 呼入通话建立完成回调(等同于完成接听):
        onCallEstablishForCallout = 'onCallEstablishForCallout', // 呼出通话建立完成回调
        onCallWillHangup = 'onCallWillHangup', // 执行挂断动作回调
        onCallDidHangup = 'onCallDidHangup', // 呼入电话挂断完成回调
        onCallOutDidHangup = 'onCallOutDidHangup', // 呼出电话挂断完成回调
        onCallTransferOut = 'onCallTransferOut', // 转接回调 0.4.16版本新增
        onCallHold = 'onCallHold', // 通话保持回调 0.4.16版本新增
        onCallRecovery = 'onCallRecovery', // 通话恢复回调 0.4.16版本新增
        'actionError.tokenExpired' = 'actionError.tokenExpired',  // token失效回调
      }
    • 注册监听Demo
      // register action error callback
      const unRegister = softPhone.register(EnumEventName['actionError.tokenExpired'], (eventData, prevConfig, data) => {
        // here! your can update BearerToken by softPhone.setConfig({BearerToken: 'xxx'});
          const { type, data } = eventData;
        console.log('[SoftPhone actionError.tokenExpired]',type, data);
      });
      unRegister() // unRegister current actionError.tokenExpired event
      eventData字段说明:
      acid: string // 通话id channelId
      agentBasicCode: "AgentBusyForCall" // 代理状态Code
      agentBasicDesc: "坐席通话忙" // 代理状态描述
      agentCallCode: "AgentCallDialOut" // 拨号状态Code
      agentCallDesc: "外拨中" // 拨号状态描述
      aid: string // 在线id
      ani: string 
      appName: string
      cmd: string
      connId: string // 话务Id
      departmentId: string
      dnis: string
      eventTime: string // 事件时间
      holdConnId: ""
      jobId: string  // 小二职位id
      mid: string // memberId?
      name: string // 会员名称
      status: AgentBarStatus // 坐席状态AgentBarStatus
      subSessionId: string // 子会话id
      supportNewFunction: string
      time: string
  3. 静态APIs列表
    const APIs = {
      renderComponent, // 渲染组件到指定节点
      doCallOut, // 执行外呼
      startWork,  // 执行开始上班, 已废弃,可由agentCheckin替代
      agentCheckin, // 执行拨号盘签入
      agentCheckout, // 执行拨号盘签出
      getAgentBarStatus, // 获取当前拨号盘状态
      callAnswer,    // 来电接听
      callHangup,            // 电话挂断
      takeWebSocketNotify, // 获取 websocket 通道实例
    }
    • 坐席签入Demo
      登录操作,状态流转到AgentBarStatus.AgentCheckout。
      const { APIs } = window.SoftPhoneSDK;
      APIs.agentCheckin(); // 是否成功可通过注册onAgentCheckedin事件来监听
    • 坐席签出Demo
      登出操作,状态流转到 AgentBarStatus.AgentCheckin。
      const { APIs } = window.SoftPhoneSDK;
      APIs.agentCheckout(); // 是否成功可通过注册onAgentCheckedout事件来监听
    • 发起呼叫Demo
      调用外呼能力,外呼成功后执行AgentBarStatus.onCallDialOut,通过返回的eventData.data执行挂断操作。
      const { APIs } = window.SoftPhoneSDK;
      
      APIs.doCallOut({
        number, // 要呼叫的号码
        onSuccess: this.onSuccess, // 外呼成功接通后的回调,参数为通话信息对象
        param: {
          number, // 要呼叫的号码
          memberId, // 必填, 匿名或未知用户可传-1
          memberName, // 必填,用户名称,未知可传 '匿名会员'
          calloutNumber, // 选填, 主叫号码,如未填则默认为配置的第一个
          fromSource: 'other_system_out', // 必填, hotlinebs_out || ticket_out || other_system_out (热线||工单||其他系统)
          taskId: this.props.common.taskId, // 如需查动作记录/服务记录时, channelId || caseId必传其中之一
          channelId: this.props.channelId, // 会话id 可选
          caseId: this.props.common.caseId, // 工单id
        }
      })
    • 挂断电话Demo
      APIs.callHangup(connId, jobId, acid, aid);
      应用场景:呼入不接起挂断、呼入接起主动挂断、拨出未接/接起主动挂断。
      // 拨入挂断
      softPhone.register(EnumEventName.AgentRinging, (eventData, prevConfig, data) => {
          const { data: { connId, jobId, acid, aid } } = eventData;
        APIs.callHangup(connId, jobId, acid, aid);
      });
      
      // 拨出挂断
      softPhone.register(EnumEventName.AgentCallDialOut, (eventData, prevConfig, data) => {
          const { data: { connId, jobId, acid, aid } } = eventData;
        APIs.callHangup(connId, jobId, acid, aid);
      });
    • 接听来电Demo
      softPhone.register(EnumEventName.AgentRinging, (eventData, prevConfig, data) => {
        // 参数可从 AgentRinging 的事件回调参数里取 
          const { data: { connId, jobId, acid, aid } } = eventData;
        APIs.callAnswer(connId, jobId, acid, aid);
      });
    • 获取坐席状态Demo
      回坐席状态,对应值对应的状态如下,初始为-1未注册。
      const { APIs } = window.SoftPhoneSDK;
      const status = await APIs.getAgentBarStatus();
      
      enum AgentBarStatus {
        AgentCheckout = 1, // 签出
        AgentCheckin = 2, // 签入
        AgentReady = 3, // 空闲
        AgentBreak = 4, // 小休
        AgentCallRelease = 5, // 通话结束(挂断的时候)
        AgentAcw = 6, // 话后处理
        AgentRinging = 7, // 振铃
        AgentCallAnswerRequest = 8, // 接听请求中
        AgentCallDialOut = 9, // 拨号请求中
        AgentCallInboundEstablish = 10, // 呼入通话
        AgentCallOutBoundEstablish = 11, // 呼出通话
        AgentCallInternalBoundEstablish = 12, // 内部通话b打给c
        AgentCallConsultEstablish = 13, // 被动求助通话建立
        AgentCallHeld = 14, // 通话保持
        AgentCallConferenceWaitAnswer = 16, // 发起三方
        AgentCallThirdConsult = 17, // 三方通话中
        AgentThirdRetrieveRequest = 18, // 三方取回中
        AgentCallConference = 19, // 会议  三方通话状态
      }
    • 获取webSocket通道
      当某些场景下我们需要定制能力时(e.g.自定义语音转文本能力),可以直接获取websocket通道,该对象提供了两个方法和一个websocket实例。
      type Subscribe = (eventName: string, callback: (msgData: any) => void) => void;
      type PhoneNotify = {
        listenerContainer: {
          [eventName: string]: Array<(msgObj: any) => void>;
        };
        on: Subscribe;
        off: Subscribe;
        socket: ReconnectingWebSocket;
      };
      
      const { APIs } = window.SoftPhoneSDK;
      const notify: PhoneNotify = APIs.takeWebSocketNotify();
      function listenerHotlineMsg(msgData) {
        // 当通道返回的 data.sceneType === 'hotline-message' 时执行
      }
      notify.on('hotline-message', listenerHotlineMsg); // 执行订阅
      notify.off('hotline-message', listenerHotlineMsg); // 注销订阅
      当前(0.4.16)已定的 sceneType有:
      • hotline-message热线消息,表示通话状态和信息。
      • voice-to-text语音转文本,表示推送实时语音同声传译后的文本内容。
  4. 多语言
    • 已支持语言枚举:
      export enum EnumLocale {
        en = 'en',
        'zh-CN' = 'zh-CN',
      }
    • 默认语言:

      zh_CN

    • 设置语言Demo:
      const { SoftPhone, EnumLocale } = window.SoftPhoneSDK;
      
      const softPhone = SoftPhone.getInstance();
      
      softPhone.setConfig({
          locale: EnumLocale.en, // default = 'zh_CN'
      });
  5. 热线状态
    当我们想对热线中状态流转有更高的定制需求时,e.g.
    • 在RTC通道(打开或关闭)时打点。
    • 在通话恢复时同步文本语音信息等。
    可以先获取到websocket实例,然后根据msgData.head.agentCallCode参照EnumWSEventType枚举热线状态进行定制逻辑处理。具体枚举:
    export enum EnumWSEventType {
      // 上班状态
      AgentCheckin = 'AgentCheckin', // 签入
      AgentCheckout = 'AgentCheckout', // 签出
      AgentReady = 'AgentReady', // 空闲
      AgentBreak = 'AgentBreak', // 小休
      AgentAcw = 'AgentAcw', // 话后处理
      
      // 通话事件监听
      AgentRinging = 'AgentRinging', // 来电
      AgentCallInboundEstablish = 'AgentCallInboundEstablish', // 呼入电话建立
      AgentCallOutBoundEstablish = 'AgentCallOutBoundEstablish', // 呼出通话建立
      AgentCallDialOut = 'AgentCallDialOut', // 拨号呼出
      AgentCallAnswerRequest = 'AgentCallAnswerRequest', // 呼入通话执行接听
      AgentCallRelease = 'AgentCallRelease', // 坐席释放
      AgentCallHeld = 'AgentCallHeld', // 通话保持
      
      // 双步转
      AgentCallConferenceWaitAnswer = 'AgentCallConferenceWaitAnswer', // 三方求助等待应答
      AgentCallConference = 'AgentCallConference', // 会议中
      AgentCallThirdConsult = 'AgentCallThirdConsult', // 三方求助通话中
      AgentThirdRetrieveRequest = 'AgentThirdRetrieveRequest', // 三方取回请求中
      AgentCallConsultThirdRetrieveReq = 'AgentCallConsultThirdRetrieveReq', // 三方求助三方取回请求中
      AgentCallConsultEstablish = 'AgentCallConsultEstablish', // 三方求助电话建立
      AgentCallInternalBoundEstablish = 'AgentCallInternalBoundEstablish', // 内部通话中
    
      // RTC
      AgentJoinChannel = 'AgentJoinChannel', // 创建 RTC 连接
      AgentLeaveChannel = 'AgentLeaveChannel' // 销毁 RTC 连接
    }
    e.g.
    const { APIs, EnumWSEventType } = window.SoftPhone;
    const notify: PhoneNotify = APIs.takeWebSocketNotify;
    function listenerHotlineMsg(msgData) {
      // 当通道返回的 data.sceneType === 'hotline-message' 时执行
      if(_get(msgData, 'head.agentCallCode') === EnumWSEventType.AgentLeaveChannel) {
          // dosomething
      }
    }
    takeWebSocketNotify.on('hotline-message', listenerHotlineMsg); // 执行订阅

完整版Demo

export function APPControll(props: RouterCommonProps) {
  const [hash, refresh] = useHash();
  const { state: { config } } = useContext(Context); // 配置托管到context
  const standWrap = useRef();

  useRegisterHooks(props.history); // 注册钩子
  useInitalIDPToken(hash); // fetch BearerToken assign to `context state` when hash changed

  useDeepEffect(() => {
    instance.setConfig(config);
    // 检测到过期 regenerator token
    const unRegister = instance.register(EnumEventName.actionError, refresh);
    // 渲染拨号条
    APIs.renderComponent(instance.getUIComponent(), standWrap.current);
    // 执行签入操作
    APIs.agentCheckin();
    return () => {
      APIs.agentCheckout();
      unRegister();
    };
  }, [config]);
  return (
        <div>
        <App />
      <div ref={standWrap} className="statusbar" />
    </div>
  )
}

// e.g. use ReactHooks Synchronization status to Context
function useRegisterHooks(history: RouterCommonProps['history']) {
  const { state, dispatch } = useContext(Context);
  useEffect(() => {
    softPhone.register(EnumEventName.onAgentCheckedin, e => {
      safeConsole('成功登录', e);
      dispatch({
        type: EnumEventName.onAgentCheckedin,
        payload: _get(e, 'data'),
      });
    });
    softPhone.register(EnumEventName.onAgentCheckedout, e => {
      safeConsole('成功登出', e);
      dispatch({
        type: EnumEventName.onAgentCheckedout,
        payload: _get(e, 'data'),
      });
    });
    softPhone.register(EnumEventName.onCallDialOut, e => {
      safeConsole('外拨请求中', e);
      // ...
    });
    softPhone.register(EnumEventName.onCallComing, e => {
      safeConsole('新来电振铃', e);
      // navigation to callpanel page...
      // 暂不支持呼入方主动挂断的事件,绕一下轮询去侦测是否放开坐席
      ;(async function() {
        while (true) {
          await sleep(1000);
          const status = await APIs.getAgentBarStatus();
          if (status === agmentBarStatus.AgentReady) {
            // 处理呼入方主动挂断逻辑...
            return false;
          }
        }
      })();
    });
    softPhone.register(EnumEventName.onCallWillAnswer, e => {
      safeConsole('呼入电话执行接听动作', e);
    });
    softPhone.register(EnumEventName.onCallEstablishForCallin, e => {
      safeConsole('已接听来电', e);
    });
    softPhone.register(EnumEventName.onCallEstablishForCallout, e => {
      safeConsole('呼出通话建立完成', e);
    });
    softPhone.register(EnumEventName.onCallWillHangup, e => {
      safeConsole('挂断回调', e);
      // navigation to pre page before callin
    });
    softPhone.register(EnumEventName.onCallDidHangup, e => {
      safeConsole('呼入电话挂断完成', e);
    });
    softPhone.register(EnumEventName.onCallOutDidHangup, e => {
      safeConsole('呼出电话挂断完成', e);
    });
    softPhone.register(EnumEventName.onCallHold, (e) => {
      safeConsole('通话保持', e);
    });
    softPhone.register(EnumEventName.onCallRecovery, () => {
      safeConsole('通话恢复');
    });
    softPhone.register(EnumEventName.onCallTransferOut, () => {
      safeConsole('转交');
    });
    softPhone.register(EnumEventName['actionError.tokenExpired'], e => {
      safeConsole('token失效', e);
      // refresh logic...
    });
    return () => /* hanlder cancel events... */
  }, []);
}

版发布记录

0.5.0

语音转文本提供呼入呼出可选自开。

0.4.17

  • 优化代码逻辑,移除非必要打印信息(sipml除外)
  • 新增抛出三个钩子(保持、恢复、转交)
0.4.16
  • 新增语音转文本插件。
  • 新增服务摘要插件。
  • 新增对外暴露websocket以支持定制功能。
  • 在线Demo更新。

0.4.15

新增本地化归属

0.4.12

  • 新增对外的接听和挂断API。
  • 新增隐藏UI的配置参数。

0.4.10

  • 归属地外化多tab不显示问题修复
  • 增加初始化配置参数-autoChangeOnLineTime用来配置话后处理时间(默认为0,不触发)
  • 增加初始化配置参数-canChangeStatusByHand用来配置是否隐藏手动操作状态栏的能力(默认为false,不需要隐藏)

0.4.9

  • 号码列表支持“号码组外呼”
  • 浮层层级提升至9999
  • 拨打电话,上下班等操作提供“同步api”
  • 增加“获取拨号盘状态”的前端api
  • fuyun api token 过期事件修复
  • 入呼、外呼归属地外化

0.4.7

  • 拨号盘内部事件通信优化,弃用msg-bus
  • iconfont更新

0.4.6

Demo页增加cdnPath的可配置化

0.4.5

  • 增加对后端接口外呼的支持
  • 支持在已加载SIPml-api的环境下依旧能够正常运行
  • 修改默认外呼来源为other_system_out

0.4.4

  • 增加双步转UI调整
  • 测试环境变量修改

0.4.3

  • 交互体验优化
  • 增加系统外呼透露
  • 增加是否脱敏展现号码的配置项
  • 增加是否可拖动的配置项
  • Fuyun和Pop空入参优化
  • 转交逻辑优化,加入咨询和会议逻辑(暂时隐藏)
  • 部分已知bug修复

附:运行正常时,图例

123456